<?php
/**
 * 词法分析器
 * 
 * 1,暂时不支持regExp字面量
 * 2,暂时不支持Unicode
 * 3,暂时不支持XML节点
 * 
 */
class LexicalAnalyzer{
    
    /**
     * 
     * @var string
     */
    public $stream;
    
    /**
     * token列表
     * @param array
     */
    public $tokenList;
    
    /**
     * comment列表
     * @var array
     */
    public $commentList;
    
    /**
     * 当前分析的token的起始位置
     * @var int
     */
    private $currentPostion;
    
    /**
     * 当前分析的token在其所在行的起始位置
     * @var int
     */
    private $currentLinePostion;
    
    /**
     * 
     * @var int
     */
    private $currentLineNum;
    
    public function getTokenList(){
        return $this->tokenList;
    }
    
    function __construct($stream) {
        $this->stream = $stream;
        $this->tokenList = array();
        $this->commentList = array();
        $this->currentPostion = -1;
        $this->currentLinePostion = -1;
        $this->currentLineNum = -1;
    }
    
    public function run(){
        // init cursor
        $this->currentPostion = 0;
        $this->currentLineNum = 0;
        
        // 开始分析
        while(true) {
            // 获取token
            $token = $this->getTokenInALine();
            
            // 一行分析完毕
            if ($token->getTokenType() === Token::EOL) {
                $this->currentLineNum ++;
                continue;
            }
            
            // 全部分析完毕
            if ($token->getTokenType() === Token::EOF) {
                break;
            }
        }
        
        return $this->tokenList;
    }
    
    /**
     * 
     * @return Token $token
     */
    private function getTokenInALine(){
        // init cursor
        $this->currentLinePostion = 0;
        
        // 开始分析
        $token = $this->_getToken();
        
        // 没有到达行末尾
        while ($token->getTokenType() !== Token::EOL && $token->getTokenType() !== Token::EOF){
            
            // 如果是注释
            if($token->getTokenType() === Token::COMMENT){
                $this->addToCommentList($token);
            }
            
            // 非注释
            else{
                // cache住
                $this->tokenList[] = $token;
            }
            
            $token = $this->_getToken();
        }
        
        // 行尾
        if($token->getTokenType() === Token::EOL || $token->getTokenType() === Token::EOF){
            return $token;
        }
    }
    
    // 真正的获取token
    private function _getToken(){
        
        // init
        $tokenLiteral = '';
        
        while(true) {
            $char = $this->getChar();
            
            // WhiteSpace
            if(WhiteSpace::isWhiteSpace($char)){
                $this->consumeChar();
                continue;
            }
            
            // COMMENTS
            if( $char === '/'){
                $tmpChar = $this->lookAhead();
                $tempStream = &$this->stream;
                $commentStart = $this->currentPostion;
                if( $tmpChar === '/' ){
                    $singleLineComment = Comment::getSingleLineComment($tempStream, $commentStart);
                    $comment = new Comment();
                    $comment->setCommentType(Comment::SingleLineComment);
                    $comment->setTokenLiteral($singleLineComment);
                    $comment->setTokenLength(strlen($singleLineComment));
                    $comment->setLineNumber($this->currentLineNum);
                    $comment->setTokenPosition($this->currentLinePostion);
                    
                    $this->consumeChar(strlen($singleLineComment));
                    return $comment;
                }
                if( $tmpChar === '*' ){
                    $multiLineCommentChars = '/*';
                    for($i = 2;;$i++){
                        $tmpChar2 = $this->lookAhead($i);
                        $tmpChar3 = $this->lookAhead($i+1);
                        
                        if( $tmpChar2 === Token::EOF || $tmpChar3 === Token::EOF){
                            throw new Exception(ERR_MSG_LEX_6, ERR_CODE_LEX_6);
                        }
                        else if($tmpChar2 === '*' && $tmpChar3 === '/'){
                            $multiLineCommentChars .= '*/';
                            
                            $comment = new Comment();
                            $comment->setCommentType(Comment::MultiLineComment);
                            $comment->setTokenLiteral($multiLineCommentChars);
                            $comment->setTokenLength(strlen($multiLineCommentChars));
                            $comment->setLineNumber($this->currentLineNum);
                            $comment->setTokenPosition($this->currentLinePostion);
                            
                            $this->consumeChar(strlen($multiLineCommentChars));
                            return $comment;
                        }
                        else{
                            $multiLineCommentChars .= $tmpChar2;
                            if(LineTerminator::isLineTerminator($tmpChar2)){
                                if(ord($tmpChar2) === 0xD && ord($tmpChar3) === 0xA){
                                    $multiLineCommentChars .= $tmpChar3;
                                    $i++;
                                }
                                
                                $this->currentLineNum++ ;
                                $this->currentLinePostion = 0;
                            }
                        }
                    }
                }
            }
            
            // EOL
            if(LineTerminator::isLineTerminator($char)){
                // windows系统(\n\r)
                if(ord($char) === 0xD){                   // 回车
                    $tmpChar = $this->lookAhead();
                    if(ord($tmpChar) === 0xA){            // 要吃进'\r'后面的'\n'换行
                        $this->consumeChar(2);
                    }
                    else{
                        $this->consumeChar();
                    }
                }
                else{
                    $this->consumeChar();
                }
                
                $token = new Token();
                $token->setTokenType(Token::EOL);
                return $token;
            }
            
            // EOF(一种特殊的Token)
            if($char === Token::EOF){
                $token = new Token();
                $token->setTokenType(Token::EOF);
                return $token;
            }
            
            // XML
            if($char === '@'){
                throw new Exception(ERR_MSG_LEX_1, ERR_CODE_LEX_1);
            }
            
            // Unicode
            if($char === '\\'){
                $tmpChar = $this->lookAhead();
                if($tmpChar === 'u' || $tmpChar === 'U'){
                    throw new Exception(ERR_MSG_LEX_2, ERR_CODE_LEX_2);
                }
            }
            
            // 判断是否满足IdentifierName，参考Ecma-262 5rd 7.6
            $isIdStart = IdentifierName::isIdentifierStart($char);
            if($isIdStart){
                $tokenLiteral .= $char;
                
                for($lookAheadCount = 1;;$lookAheadCount++){
                    $tempChar = $this->lookAhead($lookAheadCount);
                    $isIdpart = IdentifierName::isIdentifierStart($tempChar);
                    
                    // 满足IdPart
                    if($isIdpart){
                        $tokenLiteral .= $tempChar;
                        continue;
                    }
                    else{
                        break;
                    }
                }
                
                // 准备token
                $tokenLen = strlen($tokenLiteral);
                $token = new IdentifierName();
                $token->setTokenLiteral($tokenLiteral);
                $token->setTokenLength($tokenLen);
                $token->setLineNumber($this->currentLineNum);
                $token->setTokenPosition($this->currentLinePostion);
                
                // 吃进字符
                $this->consumeChar($tokenLen);
                
                return $token;
            }
            
            // 判断是否满足Punctuator，参考Ecma-262 5rd 7.7
            $isPunctuatorStart = Punctuator::isPunctuatorStart($char);
            if($isPunctuatorStart){
                $punctuator = null;
                switch ($char){
                    case '{':
                        $punctuator = new Punctuator(Token::Punctuator_LeftBrace);
                        break;
                    case '}':
                        $punctuator = new Punctuator(Token::Punctuator_RightBrace);
                        break;
                    case '(':
                        $punctuator = new Punctuator(Token::Punctuator_LeftParenthesis);
                        break;
                    case ')':
                        $punctuator = new Punctuator(Token::Punctuator_RightParenthesis);
                        break;
                    case '[':
                        $punctuator = new Punctuator(Token::Punctuator_LeftSquareBrace);
                        break;
                    case ']':
                        $punctuator = new Punctuator(Token::Punctuator_RightSquareBrace);
                        break;
                    case '.':
                        $punctuator = new Punctuator(Token::Punctuator_Dot);
                        break;
                    case ';':
                        $punctuator = new Punctuator(Token::Punctuator_Semicolon);
                        break;
                    case ',':
                        $punctuator = new Punctuator(Token::Punctuator_Comma);
                        break;
                    case '+':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '+'){
                            $punctuator = new Punctuator(Token::Punctuator_AddAdd);
                        }
                        else if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_AddEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Add);
                        }
                        break;
                    case '-':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '-'){
                            $punctuator = new Punctuator(Token::Punctuator_MinusMinus);
                        }
                        else if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_MinusEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Minus);
                        }
                        break;
                    case '*':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_AsteriskEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Asterisk);
                        }
                        break;
                    case '%':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_PercentEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Percent);
                        }
                        break;
                    case '&':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '&'){
                            $punctuator = new Punctuator(Token::Punctuator_AndEqual);
                        }
                        else if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_AndAnd);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_And);
                        }
                        break;
                    case '|':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '|'){
                            $punctuator = new Punctuator(Token::Punctuator_OrEqual);
                        }
                        else if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_OrOr);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Or);
                        }
                        break;
                    case '<':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_LessEqual);
                        }
                        else if($tempChar == '<'){
                            $tempChar2 = $this->lookAhead(2);
                            if($tempChar2 == '='){
                                $punctuator = new Punctuator(Token::Punctuator_LessLessEqual);
                            }else{
                                $punctuator = new Punctuator(Token::Punctuator_LessLess);
                            }
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Less);
                        }
                        break;
                    case '>':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_MoreEqual);
                        }
                        else if($tempChar == '>'){
                            $tempChar2 = $this->lookAhead(2);
                            if($tempChar2 == '='){
                                $punctuator = new Punctuator(Token::Punctuator_MoreMoreEqual);
                            }
                            else if($tempChar2 == '>'){
                                $tempChar3 = $this->lookAhead(3);
                                if($tempChar3 == '='){
                                    $punctuator = new Punctuator(Token::Punctuator_MoreMoreMoreEqual);
                                }else{
                                    $punctuator = new Punctuator(Token::Punctuator_MoreMoreMore);
                                }
                            }
                            else{
                                $punctuator = new Punctuator(Token::Punctuator_MoreMore);
                            }
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_More);
                        }
                        break;
                    case '^':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_ExclusiveOrEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_ExclusiveOr);
                        }
                        break;
                    case '!':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $tempChar2 = $this->lookAhead(2);
                            if($tempChar2 == '='){
                                $punctuator = new Punctuator(Token::Punctuator_NotEqualEqual);
                            }
                            else{
                                $punctuator = new Punctuator(Token::Punctuator_NotEqual);
                            }
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Not);
                        }
                        break;
                    case '~':
                        $punctuator = new Punctuator(Token::Punctuator_BitNot);
                        break;
                    case '?':
                        $punctuator = new Punctuator(Token::Punctuator_Ask);
                        break;
                    case ':':
                        $punctuator = new Punctuator(Token::Punctuator_Colon);
                        break;
                    case '=':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $tempChar2 = $this->lookAhead(2);
                            if($tempChar2 == '='){
                                $punctuator = new Punctuator(Token::Punctuator_EqualEqualEqual);
                            }
                            else{
                                $punctuator = new Punctuator(Token::Punctuator_EqualEqual);
                            }
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Equal);
                        }
                        break;
                    case '/':
                        $tempChar = $this->lookAhead(1);
                        if($tempChar == '='){
                            $punctuator = new Punctuator(Token::Punctuator_DivisionEqual);
                        }
                        else{
                            $punctuator = new Punctuator(Token::Punctuator_Division);
                        }
                        break;
                    default:
                        throw new Exception(ERR_MSG_LEX_3, ERR_CODE_LEX_3);
                }
                
                $punctuator->setLineNumber($this->currentLineNum);
                $punctuator->setTokenPosition($this->currentLinePostion);
                
                // 吃进字符
                $this->consumeChar($punctuator->getTokenLength());
                
                return $punctuator;
            }
            
            // 判断是否满足Literals，参考Ecma-262 5rd 7.8， 其中NullLiteral和BooleanLiteral已经在识别IdentifierName时处理
            // 暂时不支持RegularExpressionLiteral
            $isNumericLiteralStart = NumericLiteral::isNumericLiteralStart($char);
            if($isNumericLiteralStart === true){
                $tempStream = &$this->stream;
                $tempPos = $this->currentPostion;
                $tempChar = $this->lookAhead(1);
                $number = false;
                if($char === '0' && $tempChar === 'x'){
                    $number = NumericLiteral::getHexIntegerLiteral($tempStream,$tempPos);
                }
                else{
                    $number = NumericLiteral::getDecimalLiteral($tempStream,$tempPos);
                }
                if( $number !== false ){
                    $numericLiteral = new NumericLiteral();
                    $numericLiteral->setTokenLiteral($number);
                    $numericLiteral->setTokenLength(strlen($number));
                    $numericLiteral->setLineNumber($this->currentLineNum);
                    $numericLiteral->setTokenPosition($this->currentLinePostion);
                    
                    $this->consumeChar(strlen($number));
                    
                    return $numericLiteral;
                }
                else{
                    throw new Exception(ERR_MSG_LEX_5, ERR_CODE_LEX_5);
                }
            }
            
            $isStringLiteralStart = StringLiteral::isStringLiteralStart($char);
            if($isStringLiteralStart === StringLiteral::DoubleString || $isStringLiteralStart === StringLiteral::SingleString){
                $stringBuffer = '';
                $tempStream = &$this->stream;
                $tempPos = $this->currentPostion+1;
                while ( ($stringCharacter = StringLiteral::getStringCharacter($isStringLiteralStart,$tempStream,$tempPos)) !== false ){
                    $stringBuffer = $stringBuffer . $stringCharacter;
                    $tempPos = $tempPos + strlen($stringCharacter);
                }
                
                $stringEnd = $tempStream[$tempPos];
                if( ($isStringLiteralStart === StringLiteral::DoubleString && $stringEnd== '"') 
                  ||($isStringLiteralStart === StringLiteral::SingleString && $stringEnd== "'")){
                    $stringBufferLength = strlen($stringBuffer);
                    
                    $stringLitera = new StringLiteral();
                    $stringLitera->setStringLiteralType($isStringLiteralStart);
                    $stringLitera->setTokenLiteral($stringBuffer);
                    $stringLitera->setTokenLength($stringBufferLength);
                    $stringLitera->setLineNumber($this->currentLineNum);
                    $stringLitera->setTokenPosition($this->currentLinePostion);
                    
                    $this->consumeChar($stringBufferLength+2);
                    
                    return $stringLitera;
                }
                else{
                    throw new Exception(ERR_MSG_LEX_4, ERR_CODE_LEX_4);
                }
            }
            
            $this->consumeChar();
        }
    }
    
    /**
     * 获取字符
     * 暂时不考虑编码问题
     * 
     * getChar不会吃进字符
     */
    private function getChar(){
        $pos = $this->currentPostion;
        $len = strlen($this->stream);
        
        // 如果到达了stream的末尾
        if($pos >= $len){
            return Token::EOF;
        }
        
        $char = $this->stream[$pos];
        return $char;
    }
    
    /**
     * 向前看1个字符
     * 
     * lookAhead不会吃进字符
     */
    private function lookAhead($n=1){
        $pos = $this->currentPostion + $n;
        $len = strlen($this->stream);
        // 如果到达了stream的末尾
        if($pos >= $len){
            return Token::EOF;
        }
        
        $char = $this->stream[$pos];
        return $char;
    }
    
    /**
     * 吃进字符
     * 
     * @param int $n
     */
    private function consumeChar($n=1){
        $this->currentPostion = $this->currentPostion + $n;
        $this->currentLinePostion = $this->currentLinePostion + $n;
    }
    
    private function addToCommentList($comment){
        $this->commentList[] = $comment;
    }
}